﻿#region Copyright (c) Nicola Gallo All rights reserved.
/*
 * All rights reserved. Reproduction of trasmission of this file, or a portion thereof,
 * is forbidden without prior written permission of Nicola Gallo.
 * 
 * Author: Nicola Gallo
 * Date: 09/04/2011
 * 
 * */
#endregion
namespace PrismMVC.Shell.MVC
{
    //.NET References
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Windows.Controls;

    //Microsoft References
    using Microsoft.Practices.Prism.Modularity;
    using Microsoft.Practices.Prism.Logging;
    using Microsoft.Practices.Prism.Events;
    using Microsoft.Practices.Unity;

    //PrismMVC References
    using PrismMVC.Shell.Containers;
    using PrismMVC.Shell.Data;

    /// <summary>
    /// Defines the controller class.
    /// </summary>
    public class Controller : IController, ISupportViewData
    {
        //Events

        /// <summary>
        /// Occurs when the controller is navigating.
        /// </summary>
        public event EventHandler<ControllerEventArgs> Navigating;

        //Fields

        private ViewDataDictionary _viewData;

        //Constructors

        /// <summary>
        /// Creates a new instance of <see cref="Controller"/>.
        /// </summary>
        /// <param name="unityContainer">Unity container.</param>
        /// <param name="shellProxy">Shell proxy.</param>
        /// <param name="loggerFacade">Logger facade.</param>
        /// <param name="eventAggregator">Event aggregator.</param>
        public Controller(IUnityContainer unityContainer, IShellProxy shellProxy, ILoggerFacade loggerFacade, IEventAggregator eventAggregator)
        {
            if (unityContainer == null)
            {
                throw new ArgumentNullException("UnityContainer parameter can not be equals to null.");
            }
            else if (shellProxy == null)
            {
                throw new ArgumentNullException("ShellProxy parameter can not be equals to null.");
            }
            else if (loggerFacade == null)
            {
                throw new ArgumentNullException("LoggerFacade parameter can not be equals to null.");
            }
            else if (eventAggregator == null)
            {
                throw new ArgumentNullException("EventAggregator parameter can not be equals to null.");
            }
            this.UnityContainer = unityContainer;
            this.ShellProxy = shellProxy;
            this.LoggerFacade = loggerFacade;
            this.EventAggregator = eventAggregator;
        }

        //Properties

        /// <summary>
        /// Gets <see>ViewDataDictionary</see> identifier.
        /// </summary>
        public string ViewDataID { get; private set; }

        /// <summary>
        /// Gets the <see cref="ViewDataDictionary"/>.
        /// </summary>
        public ViewDataDictionary ViewData
        {
            get
            {
                if (this._viewData == null)
                {
                    this._viewData = ViewDataDictionaryFactory.CreateViewDataDictionary(this.ViewDataID);
                }
                return this._viewData;
            }
        }

        /// <summary>
        /// Gets the shell proxy.
        /// </summary>
        private IShellProxy ShellProxy { get; set; }

        /// <summary>
        /// Gets the shell proxy.
        /// </summary>
        protected IUnityContainer UnityContainer { get; set; }

        /// <summary>
        /// Gets the logger facade.
        /// </summary>
        protected ILoggerFacade LoggerFacade { get; private set; }

        /// <summary>
        /// Gets the event aggregator.
        /// </summary>
        protected IEventAggregator EventAggregator { get; private set; }

        /// <summary>
        /// Gets/Sets the current view.
        /// </summary>
        public ViewResult CurrentView { get; private set; }

        /// <summary>
        /// Gets/Sets the view host.
        /// </summary>
        internal IView ViewHost { get; set; }

        /// <summary>
        /// Gets the region name.
        /// </summary>
        public String RegionName { get; internal set; }

        /// <summary>
        /// Gets the module.
        /// </summary>
        protected internal PrismMVC.Shell.Module Module { get; internal set; }

        //Methods

        /// <summary>
        /// Sets the <see cref="ViewData"/> identifier.
        /// </summary>
        /// <param name="ViewDataID">ViewDataDictionary identifier.</param>
        internal void SetsViewDataID(String ViewDataID)
        {
            if (string.IsNullOrWhiteSpace(ViewDataID))
            {
                throw new ArgumentException("ViewDataID paramater can not be equals to either null or empty string.");
            }
            this.ViewDataID = ViewDataID;
        }

        /// <summary>
        /// Initializes the controller.
        /// </summary>
        public virtual void Initialize() { }

        /// <summary>
        /// Runs the controller.
        /// </summary>
        public void Run()
        {
            this.OnRun();
        }

        /// <summary>
        /// Called when the Run method is invoked.
        /// </summary>
        protected virtual void OnRun() { }

        /// <summary>
        /// Strops the controller.
        /// </summary>
        public void Stop()
        {
            this.OnStop();
        }

        /// <summary>
        /// Called when the Stop method is invoked.
        /// </summary>
        protected virtual void OnStop() { }

        /// <summary>
        /// Invokes an action.
        /// </summary>
        /// <param name="actionName">Action name.</param>
        /// <param name="args">Arguments.</param>
        /// <returns>Action result</returns>
        public ActionResult InvokeAction(String actionName, object args)
        {
            ActionResult actionResult = null;
            if (!String.IsNullOrWhiteSpace(actionName))
            {
                List<Tuple<Type, Object, String>> typeValues = new List<Tuple<Type, object, String>>();
                if (args != null)
                {
                    Type anonymousType = args.GetType();
                    PropertyInfo[] propertyInfos = anonymousType.GetProperties();
                    if (propertyInfos != null)
                    {
                        foreach (var propertyInfo in propertyInfos)
                        {
                            Type type = propertyInfo.PropertyType;
                            object value = propertyInfo.GetValue(args, null);
                            String name = propertyInfo.Name;
                            typeValues.Add(new Tuple<Type, object, String>(type, value, name));
                        }
                    }
                }
                var queryTypes = from typeValue in typeValues
                                 select typeValue.Item1;
                Type controllerType = this.GetType();
                MethodInfo methodInfo = controllerType.GetMethod(actionName, queryTypes.ToArray());
                if (methodInfo == null)
                {
                    MethodInfo[] methodInfos = controllerType.GetMethods();
                    if (methodInfos != null)
                    {
                        methodInfos = (from mInfo in methodInfos
                                       where mInfo.Name.Equals(actionName)
                                       select mInfo).ToArray();
                        if (methodInfos != null)
                        {
                            foreach (var mInfo in methodInfos)
                            {
                                ParameterInfo[] paramterInfos = mInfo.GetParameters();
                                if (paramterInfos != null)
                                {
                                    bool found = true;
                                    List<Type> parameters = new List<Type>();
                                    foreach (var paramterInfo in paramterInfos)
                                    {
                                        Tuple<Type, object, String> tuple = (from typeValue in typeValues
                                                                             where typeValue.Item3.Equals(paramterInfo.Name)
                                                                             select typeValue).FirstOrDefault();
                                        if ((tuple.Item1 != null) && (paramterInfo.ParameterType != tuple.Item1))
                                        {
                                            if (paramterInfo.ParameterType == typeof(String))
                                            {
                                                parameters.Add(typeof(String));
                                            }
                                            else
                                            {
                                                found = false;
                                                break;
                                            }
                                        }
                                        else
                                        {
                                            parameters.Add(tuple.Item1);
                                        }
                                    }
                                    if (found)
                                    {
                                        methodInfo = controllerType.GetMethod(actionName, parameters.ToArray());
                                    }
                                }
                                if (methodInfo != null)
                                {
                                    break;
                                }   
                            }
                        }
                    }
                }
                if ((methodInfo != null) && (typeof(ActionResult).IsAssignableFrom(methodInfo.ReturnType)))
                {
                    var queryObjects = from typeValue in typeValues
                                       select typeValue.Item2;
                    actionResult = methodInfo.Invoke(this, queryObjects.ToArray()) as ActionResult;
                    if (actionResult is ViewResult)
                    {
                        ViewResult viewResult = actionResult as ViewResult;
                        viewResult.View = (IView)this.UnityContainer.Resolve(viewResult.ViewType);
                        if (viewResult.View is View)
                        {
                            ((View)viewResult.View).SetViewDataID(this.ViewDataID);
                        }
                        this.CurrentView = viewResult;
                        EventHandler<ControllerEventArgs> navigatingHandler = this.Navigating as EventHandler<ControllerEventArgs>;
                        if (navigatingHandler != null)
                        {
                            navigatingHandler(this, new ControllerEventArgs(this.CurrentView));
                        }
                    }
                }
            }
            return actionResult;
        }
    }
}